import os.path
import socket
import threading
import time
import re
import smtplib
from email.mime.text import MIMEText
from email.header import Header

print('Mail proxy server running with pid: ', os.getpid())
# M = []
# sender = 1


class Mail:
    User = ""
    Pass = ""
    From = ""
    To = []
    data = []
    name = ""
    sum = {}

    def __init__(self, name):
        self.User = ""
        self.Pass = ""
        self.From = ""
        self.To = []
        self.data = []
        self.sum = {}
        self.name = name

    def construct(self):
        self.sum["name"] = self.name
        self.sum["User"] = self.User
        self.sum["Pass"] = self.Pass
        self.sum["MAIL FROM"] = self.From
        self.sum["RCPT TO"] = self.To
        self.sum["data"] = self.data
        return self.sum

    def show(self):
        print(self.sum)


class Receive:
    replies = {"220": "220 127.0.0.1:20221 Service ready\r\n",
               "221": "221 Bye",
               "235": "235 Authentication successful\r\n",
               "250OK": "250 OK\r\n",
               "250auth": "250-mail\r\n"
                          "250-PIPELINING\r\n"
                          "250-AUTH LOGIN PLAIN \r\n"
                          "250-AUTH=LOGIN PLAIN\r\n"
                          "250-coremail 1Uxr2xKj7kG0xkI17xGrU7I0s8FY2U3Uj8Cz28x1UUUUU7Ic2I0Y2UrWIFuGUCa0xDrUUUUj\r\n"
                          "250-STARTTLS\r\n"
                          "250 8BITMIME\r\n",
               "334User": "334 User\r\n",
               "334Pass": "334 Pass\r\n",
               "354": "354 End data with<CR><LF>.<CR><LF>\r\n",
               "500": "500 Syntax error, command unrecognized\r\n"}
    commands_origin = ["EHLO", "AUTH LOGIN", "User", "Pass", "MAIL FROM", "RCPT TO", "DATA", "QUIT"]
    commands = []

    def __init__(self, con):
        self.conn = con
        self.commands.extend(self.commands_origin)

    def start(self,sender):
        try:
            self.conn.send(bytes(self.replies["220"].encode()))  # require for name
            info = self.conn.recv(1024).decode()
            if self.commands.pop(0) in info:
                m = Mail(info)  # receive name
                info = self.auth(m)
                if info:
                    info = self.prepare(m)
                    if info:
                        info = self.data(m, info)
                        if info:
                            sender.M.append(m.construct())
                            print("The mail has been appended to the mail list")
                            print("\n")
                            return 0
                        else:
                            self.error()
                            return -1
                    else:
                        self.error()
                        return -1
                else:
                    self.error()
                    return -1
            else:
                self.error()
                return -1

        except ConnectionResetError:
            print("Client disconnected")

    def close(self):
        self.conn.close()

    def error(self):
        self.conn.send(bytes(self.replies["500"].encode()))
        print("[<] ", self.replies["500"])

    def auth(self, m):
        self.conn.send(bytes(self.replies["250auth"].encode()))
        print("[<] ", self.replies["250auth"], m.name.replace("\r\n", ""))
        info0 = self.conn.recv(1024).decode()  # auth info
        if self.check(info0):
            print("[>]", info0.replace("\r\n", ""))
            self.conn.send(bytes(self.replies["334User"].encode()))  # require for User
            print("[<] ", self.replies["334User"])
            info0 = self.conn.recv(1024).decode()
            self.commands.pop(0)
            if self.check(info0):
                m.User = info0
                print("[>]", m.User.replace("\r\n", ""))
                self.conn.send(bytes(self.replies["334Pass"].encode()))  # require for pass
                print("[<] ", self.replies["334Pass"])
                info0 = self.conn.recv(1024).decode()
                self.commands.pop(0)
                if self.check(info0):
                    m.Pass = info0
                    print("[>]", m.Pass.replace("\r\n", ""))
                    self.conn.send(bytes(self.replies["235"].encode()))  # require for pass
                    print("[<] ", self.replies["235"])
                    self.commands.pop(0)
                    return 1
                else:
                    self.error()
                    return 0
            else:
                self.error()
                return 0
        else:
            self.error()
            return 0

    def prepare(self, m):
        info0 = self.conn.recv(1024).decode()  # receive FROM
        if self.check(info0) and self.commands.pop(0) in info0 and self.checkEmail(info0):
            m.From = info0  # receive FROM
            print("[>]", m.From.replace("\r\n", ""))
            self.conn.send(bytes(self.replies["250OK"].encode()))  # From OK
            print("[<] ", self.replies["250OK"])
            info0 = self.conn.recv(1024).decode()
            if self.check(info0) and self.commands[0] in info0 and self.checkEmail(info0):
                while self.commands[0] in info0:
                    if self.check(info0) and self.checkEmail(info0):
                        m.To.append(info0)
                        print("[>] ", info0.replace("\r\n", ""))
                        self.conn.send(bytes(self.replies["250OK"].encode()))  # From OK
                        print("[<] ", self.replies["250OK"])
                        info0 = self.conn.recv(1024).decode()
                    else:
                        self.error()
                        return 0
                self.commands.pop(0)
                return info0
            else:
                self.error()
                return 0
        else:
            self.error()
            return 0

    def data(self, m, info1):
        if self.commands.pop(0) in info1:  # DATA
            print("[>] ", info1.replace("\r\n", ""))
            self.conn.send(bytes(self.replies["354"].encode()))  # require for data
            print("[<] ", self.replies["354"])
            d = self.conn.recv(1024).decode()
            m.data.append(d)
            print("[>] ", d)
            while "\r\n.\r\n" not in d:
                d = self.conn.recv(1024).decode()
                m.data.append(d)
                print("[>] ", d)
            self.conn.send(bytes(self.replies["250OK"].encode()))
            print("[<] ", self.replies["250OK"])
            info2 = self.conn.recv(1024).decode()
            if self.commands.pop(0) in info2:
                print("[>] ", info2.replace("\r\n", ""))
                self.conn.send(bytes(self.replies["221"].encode()))
                print("[<] ",self.replies["221"])
                print("")
                return 1
        else:
            self.error()
            return 0

    def check(self, msg):
        if "\r\n" in msg:
            return 1
        else:
            self.error()
            return 0

    def checkEmail(self, name):
        if re.findall('[0-9a-zA-Z_]+@[0-9a-zA-Z.]*(?=(cn|com|gov))', name):
            return 1
        else:
            return 0


def listen(host, port):
    sender = Sender()
    s = socket.socket()
    print('Listening {}:{}'.format(host, port))
    s.bind((host, port))
    s.listen(5)
    while True:
        print("Mail list:",len(sender.M))
        if sender.flag != 0 and len(sender.M) != 0:
            print("Sender working")
            send_thread = threading.Thread(target=sender.start())
            send_thread.daemon = True
            sender.flag = 0
            send_thread.start()
            sender.flag = 1
        conn, (host, port) = s.accept()
        print('Connect: ', host, port)
        r = Receive(conn)
        r.start(sender)
        r.close()


class Sender:
    code = ["220", "334", "235", "250", "221", "354", "xxx"]

    def __init__(self):
        self.flag = 1
        self.M = []
        self.msg = b""
        self.info = "xxx"

    def send(self, s, r):
        self.msg = bytes(r.encode())
        s.send(self.msg)
        print("[<<]", self.msg.decode().replace("\r\n",""))
        self.info = s.recv(1024).decode()
        print("[>>]", self.info)

    def check(self):
        if self.info[0:3] not in self.code:
            return 0
        else:
            return 1

    def report(self, m):

        mail_host = "smtp.qq.com"
        mail_user = "274588969@qq.com"
        mail_pass = "dackibvfgrxkbjic"
        text = "An error occurred while sending the message " + self.msg.decode() + ", the server return " + self.info
        reg = re.compile("(?<=<)[^>]*")
        From = re.findall(reg, m["MAIL FROM"])[0]
        message = MIMEText(text, 'plain', 'utf-8')
        message['From'] = Header('274588969@qq.com', 'utf-8')
        message['To'] = Header(From, 'utf-8')
        subject = 'Error report'
        message['Subject'] = Header(subject, 'utf-8')

        try:
            smtp_obj = smtplib.SMTP()
            smtp_obj.connect(mail_host, 587)
            smtp_obj.login(mail_user, mail_pass)
            smtp_obj.sendmail('274588969@qq.com', [From], message.as_string())
            print("Error report sent to", From)
            self.info = "xxx"
            self.msg = b""

        except smtplib.SMTPException:
            print("Cannot send the error report to", From)
            self.info = "xxx"
            self.msg = b""

    def start(self):
        while len(self.M):
            m = self.M.pop()
            s = socket.socket()
            host = "123.126.97.5"
            port = 25
            s.connect((host, port))
            self.info = s.recv(1024).decode()
            print("[>>]", self.info)
            if self.check() == 0:
                self.report(m)
                continue
            self.send(s, m["name"])
            if self.check() == 0:
                self.report(m)
                continue
            self.send(s, "AUTH LOGIN\r\n")
            if self.check() == 0:
                self.report(m)
                continue
            self.send(s, m["User"])
            if self.check() == 0:
                self.report(m)
                continue
            self.send(s, m["Pass"])
            if self.check() == 0:
                self.report(m)
                continue
            self.send(s, m["MAIL FROM"])
            if self.check() == 0:
                self.report(m)
                continue
            fl = 1
            for i in m["RCPT TO"]:
                self.send(s, i)
                if self.check() == 0:
                    self.report(m)
                    fl = 0
                    break
            if fl != 1:
                self.report(m)
                continue
            self.send(s, "DATA\r\n")
            if self.check() == 0:
                self.report(m)
                continue
            for i in m["data"]:
                self.msg = bytes(i.replace("\r\n.\r\n", "").encode())
                s.send(self.msg)
                print("[<<]", self.msg.decode())
            self.send(s, "\r\n.\r\n")
            if self.check() == 0:
                self.report(m)
                continue
            self.send(s, "QUIT\r\n")
            if self.check() == 0:
                self.report(m)
                continue
            s.close()
        print("Sender released")
        print("\n")
        self.flag = 1
        self.msg = ""
        self.info = ""



def main():
    host, port = ('127.0.0.1', 20221)
    t = threading.Thread(target=listen, args=(host, port))
    t.daemon = True
    t.start()

    while True:
        try:
            time.sleep(10)
        except KeyboardInterrupt:
            print('KeyboardInterrupt')
            return 0


if __name__ == "__main__":
    main()
    print('Service terminated')
